#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <hw/i2c.h>

#define I2C_LOCK_RETRY_TIMES	10
#define I2C_LOCK_RETRY_INTERVAL	1 /* ms */
#define I2C_RETRY_TIMES			20
#define I2C_RETRY_INTERVAL		5 /* ms */

#define I2C_DUMP_MAX_CNT		256

int capture_i2c_open(const char *path)
{
	if(!path || path[0] == '\0') {
		return ENODEV;
	}

	return open(path, O_RDWR);
}

int capture_i2c_close(int fd)
{
	return close(fd);
}

int capture_i2c_set_speed(int fd, uint32_t speed)
{
	return devctl(fd, DCMD_I2C_SET_BUS_SPEED, &speed, sizeof(speed), NULL);
}

int capture_i2c_rd(int fd, uint32_t dev_addr, uint8_t reg_addr, uint8_t *data)
{
	int ret, ret2;
	int i2c_retry = 0;
	int lock_retry = 0;

	if (fd < 0)
		return ENODEV;

	if (!data)
		return EINVAL;

	struct send_recv {
		i2c_sendrecv_t hdr;
		uint8_t buf[1];
	} i2c_data;

	i2c_data.hdr.slave.addr = dev_addr;
	i2c_data.hdr.slave.fmt = I2C_ADDRFMT_7BIT;
	i2c_data.hdr.send_len = 1;
	i2c_data.hdr.recv_len = 1;
	i2c_data.hdr.stop = 1;
	i2c_data.buf[0] = reg_addr;

	/* i2c lock */
	for (lock_retry = 0; lock_retry < I2C_LOCK_RETRY_TIMES; lock_retry++) {
		ret = devctl(fd, DCMD_I2C_LOCK, NULL, 0, NULL);
		if (ret == EOK)
			break;
		delay(I2C_LOCK_RETRY_INTERVAL);
	}
	if (lock_retry >= I2C_LOCK_RETRY_TIMES) {
		goto error;
	}

	/* i2c read */
	for (i2c_retry = 0; i2c_retry < I2C_RETRY_TIMES; i2c_retry++) {
		ret2 = devctl(fd, DCMD_I2C_SENDRECV, &i2c_data, sizeof(i2c_data), NULL);
		if (ret2 == EOK)
			break;
		delay(I2C_RETRY_INTERVAL);
	}

	/* i2c unlock */
	for (lock_retry = 0; lock_retry < I2C_LOCK_RETRY_TIMES; lock_retry++) {
		ret = devctl(fd, DCMD_I2C_UNLOCK, NULL, 0, NULL);
		if (ret == EOK)
			break;
		delay(I2C_LOCK_RETRY_INTERVAL);
	}
	if (lock_retry >= I2C_LOCK_RETRY_TIMES) {
		goto error;
	}

	if (i2c_retry >= I2C_RETRY_TIMES) {
		ret = ret2;
		goto error;
	}

	*data = i2c_data.buf[0];
	return 0;

error:
	return ret;
}

int capture_i2c_wr(int fd, uint32_t dev_addr, uint8_t reg_addr, uint8_t data)
{
	int ret, ret2;
	int i2c_retry = 0;
	int lock_retry = 0;

	struct send_recv
	{
		i2c_send_t hdr;
		uint8_t buf[2];
	} i2c_data;

	if (fd < 0)
		return ENODEV;

	i2c_data.hdr.slave.addr = dev_addr;
	i2c_data.hdr.slave.fmt = I2C_ADDRFMT_7BIT;
	i2c_data.hdr.len = 2;
	i2c_data.hdr.stop = 1;
	i2c_data.buf[0] = reg_addr;
	i2c_data.buf[1] = data;

	/* i2c lock */
	for (lock_retry = 0; lock_retry < I2C_LOCK_RETRY_TIMES; lock_retry++) {
		ret = devctl(fd, DCMD_I2C_LOCK, NULL, 0, NULL);
		if (ret == EOK)
			break;
		delay(I2C_LOCK_RETRY_INTERVAL);
	}
	if (lock_retry >= I2C_LOCK_RETRY_TIMES) {
		goto error;
	}

	/* i2c write */
	for (i2c_retry = 0; i2c_retry < I2C_RETRY_TIMES; i2c_retry++) {
		ret2 = devctl(fd, DCMD_I2C_SEND, &i2c_data, sizeof(i2c_data), NULL);
		if (ret2 == EOK) {
			break;
		}
		delay(I2C_RETRY_INTERVAL);
	}

	/* i2c unlock */
	for (lock_retry = 0; lock_retry < I2C_LOCK_RETRY_TIMES; lock_retry++) {
		ret = devctl(fd, DCMD_I2C_UNLOCK, NULL, 0, NULL);
		if (ret == EOK)
			break;
		delay(I2C_LOCK_RETRY_INTERVAL);
	}
	if (lock_retry >= I2C_LOCK_RETRY_TIMES) {
		goto error;
	}

	if (i2c_retry >= I2C_RETRY_TIMES) {
		ret = ret2;
		goto error;
	}

	return 0;

error:
	return ret;
}

int capture_i2c_dump(int fd, uint32_t dev_addr, uint8_t reg_addr, uint8_t *data, uint32_t size)
{
	int ret, ret2;
	int i2c_retry = 0;
	int lock_retry = 0;
	int i;

	if (!data || size > I2C_DUMP_MAX_CNT) {
		ret = EINVAL;
		goto error;
	}

	if (fd < 0)
		return ENODEV;

	if (size == 0)
		return 0;

	struct send_recv {
		i2c_sendrecv_t hdr;
		uint8_t buf[I2C_DUMP_MAX_CNT];
	} i2c_data;

	i2c_data.hdr.slave.addr = dev_addr;
	i2c_data.hdr.slave.fmt = I2C_ADDRFMT_7BIT;
	i2c_data.hdr.send_len = 1;
	i2c_data.hdr.recv_len = 256;
	i2c_data.hdr.stop = 1;
	i2c_data.buf[0] = reg_addr;

	/* i2c lock */
	for (lock_retry = 0; lock_retry < I2C_LOCK_RETRY_TIMES; lock_retry++) {
		ret = devctl(fd, DCMD_I2C_LOCK, NULL, 0, NULL);
		if (ret == EOK)
			break;
		delay(I2C_LOCK_RETRY_INTERVAL);
	}
	if (lock_retry >= I2C_LOCK_RETRY_TIMES) {
		goto error;
	}

	/* i2c read */
	for (i2c_retry = 0; i2c_retry < I2C_RETRY_TIMES; i2c_retry++) {
		ret2 = devctl(fd, DCMD_I2C_SENDRECV, &i2c_data, sizeof(i2c_data), NULL);
		if (ret2 == EOK)
			break;
		delay(I2C_RETRY_INTERVAL);
	}

	/* i2c unlock */
	for (lock_retry = 0; lock_retry < I2C_LOCK_RETRY_TIMES; lock_retry++) {
		ret = devctl(fd, DCMD_I2C_UNLOCK, NULL, 0, NULL);
		if (ret == EOK)
			break;
		delay(I2C_LOCK_RETRY_INTERVAL);
	}
	if (lock_retry >= I2C_LOCK_RETRY_TIMES) {
		goto error;
	}

	if (i2c_retry >= I2C_RETRY_TIMES) {
		ret = ret2;
		goto error;
	}

	for (i = 0; i < size; i++)
		data[i] = i2c_data.buf[i];

	return size;

error:
	return ret;
}
